/**********************************************************/
/* Goblin Defense - A tower defense game for Wii Homebrew */
/* Copyright (C) 2009 Ricardo Bueno Cordeiro              */
/* Licensed under de GNU General Public License v2.0      */
/*   See license_gplv2.txt for detalis                    */
/**********************************************************/

#include <gccore.h>
#include <malloc.h>
#include <string.h>

#include <png.h>
#include <pngconf.h>

struct buffer_read_info {
	const u8 *png_buffer;
	u32 png_size;
	u32 pos;
};

void read_from_buffer(png_structp png, png_bytep data, png_size_t length) {
	struct buffer_read_info *read_info = (struct buffer_read_info *)png_get_io_ptr(png);
	memcpy(data, read_info->png_buffer + read_info->pos, length);
	read_info->pos += length;
}

void user_error_fn(png_structp png_ptr, png_const_charp error_msg) {
	longjmp(png_jmpbuf(png_ptr), 1);
}

void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg) {
}

u32 *load_png_to_RGBA(const u8 *png_buffer, u32 png_size, u32 *width, u32 *height) {
	struct buffer_read_info read_info;

	read_info.png_buffer = png_buffer;
	read_info.png_size = png_size;
	read_info.pos = 0;
	
	png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, user_error_fn, user_warning_fn);
	if (!png)
		return NULL;

	png_infop info = png_create_info_struct(png);
	if (!info) {
		png_destroy_read_struct(&png, NULL, NULL);
		return NULL;
	}

	if (setjmp(png_jmpbuf(png))) {
		png_destroy_read_struct(&png, &info, NULL);
		return NULL;
	}

	png_set_read_fn(png, &read_info, read_from_buffer);
	png_read_info(png, info);

	int bit_depth;
	int color_type;
	int interlace_type;
	int compression_type;
	int filter_method;

	png_get_IHDR(png, info, (png_uint_32 *)width, (png_uint_32 *)height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_method);

	if (color_type == PNG_COLOR_TYPE_PALETTE)
		png_set_palette_to_rgb(png);
	if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
		png_set_gray_1_2_4_to_8(png);
	if (png_get_valid(png, info, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha(png);
	if (bit_depth < 8)
		png_set_packing(png);
	if (bit_depth == 16)
		png_set_strip_16(png);
	if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb(png);
	png_set_filler(png, 0xffff, PNG_FILLER_AFTER);
	
	png_read_update_info(png, info);	

	png_get_IHDR(png, info, (png_uint_32 *)width, (png_uint_32 *)height, &bit_depth, &color_type, &interlace_type, &compression_type, &filter_method);

	u32 bytes_per_row = png_get_rowbytes(png, info);
	u32 bytes_per_pixel = bytes_per_row / (*width);
	
	if ((color_type != PNG_COLOR_TYPE_RGB) && (color_type != PNG_COLOR_TYPE_RGB_ALPHA) && (bytes_per_pixel != 4)) {
		png_destroy_read_struct(&png, &info, NULL);
		return NULL;
	}

	u8 *buffer = (u8 *) memalign(32, bytes_per_row * (*height));
	if (buffer == NULL) {
		png_destroy_read_struct(&png, &info, NULL);
		return NULL;
	}

	png_bytep *rows = new png_bytep[*height];
	if (rows == NULL) {
		free(buffer);
		png_destroy_read_struct(&png, &info, NULL);
		return NULL;
	}

	for (u32 i = 0; i < *height; i++)
		rows[i] = buffer + bytes_per_row * i;

	png_read_image(png, rows);

	delete [] rows;

	png_read_end(png, NULL);
	
	png_destroy_read_struct(&png, &info, NULL);

	return (u32 *)buffer;
}
